﻿/*
 * This file is part of MonoSettlers.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://opensettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


#if EMBEDDED
    using OpenTK.Graphics.ES20;
#else
using OpenTK.Graphics.OpenGL;
#endif

namespace MonoSettlers.RenderSystem
{
    /// <summary>
    /// This class provides the finest level of control about visual rendering.
    /// It will render the given native texture according to all other given
    /// parameters.
    /// </summary>
    /// <remarks>
    /// At the beginning it might be confusing what exactly a width or height value,
    /// for example, means. The thing is that it depends on configuration. See
    /// <see cref="GLRenderer.SetupProjection"/> for more information. 
    /// Just thread virtual pixels as real pixel size, only scaled by internal parameters.
    /// 
    /// With OpenGL always keep in mind that the we have to call ALL GL commands
    /// withtin the same thread, usually the internal render loop thread. So any
    /// methods and properties exposed to the public MUSTN'T call any GL commands,
    /// instead the calls shall be deferred.
    /// </remarks>
    public class RenderableVisual : IRenderableVisual
    {
        private static Random m_RenderShiftRandom = new Random();
        private double m_RenderShift;

        /// <summary>
        /// This is used internally by the parent for fast list operations.
        /// </summary>
        internal LinkedListNode<RenderableVisual> Node { get; set; }
        /// <summary>
        /// The ratio between width and height initially passed to the constructor.
        /// </summary>
        public double AspectRatio { get; set; }
        /// <summary>
        /// Internally allows to set a custom texture at any time, may also be null.
        /// </summary>
        internal NativeTexture Texture { get; set; }
        /// <summary>
        /// A positionable object providing this visual with an X and Y offset.
        /// </summary>
        public IPositionTracker PositionTracker { get; set; }
        /// <summary>
        /// Gets or sets the width of the visual in virtual pixels and adjusts the <see cref="Height"/>
        /// according to <see cref="AspectRatio"/>.
        /// </summary>
        public double Width { get; set; }
        /// <summary>
        /// Gets or sets the height of the visual in virtual pixels and adjusts the <see cref="Width"/>
        /// according to <see cref="AspectRatio"/>.
        /// </summary>
        public double Height { get { return Width / AspectRatio; } set { Width = value * AspectRatio; } }
        /// <summary>
        /// The parent renderer.
        /// </summary>
        public GLRenderer Parent { get; private set; }
        /// <summary>
        /// Default is one, and denotes the scaling factor for the whole rendered object.
        /// </summary>
        public Double Scale { get; internal set; }
        /// <summary>
        /// Should be rendered at all?
        /// </summary>
        public bool IsVisible { get; set; }
        /// <summary>
        /// Set for internal use. The Z-Value calculated in the first render pass.
        /// </summary>
        internal Double ZValue { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inParent">The parent renderer.</param>
        internal RenderableVisual(GLRenderer inParent)
        {
            if (inParent == null)
                throw new ArgumentNullException();

            Parent = inParent;
            Scale = 1;
            IsVisible = true;
            m_RenderShift = m_RenderShiftRandom.NextDouble() / 100;
        }

        /// <summary>
        /// Renders the object with default parameters.
        /// </summary>
        internal virtual void Render()
        {
            Render(0, 0, Width, Height);
        }

        /// <summary>
        /// Is intended for derived classes and provides a way to fine tune the final
        /// size of the rendered object according to additional frame size and offsets.
        /// </summary>
        /// <param name="inOffsetX">Virtual pixel offset in X direction.</param>
        /// <param name="inOffsetY">Virtual pixel offset in Y direction.</param>
        /// <param name="inWidth">Virtual pixel width; overrides the instance width for this call.</param>
        /// <param name="inHeight">Virtual pixel height; overrides the instance height for this call.</param>
        internal void Render(Double inOffsetX, Double inOffsetY, Double inWidth, Double inHeight)
        {
            IPositionTracker tracker = PositionTracker;

            if ((tracker == null) || !IsVisible)
                return;

            double x = tracker.Position.X + inOffsetX;
            double y = tracker.Position.Y + inOffsetY;

            /*
             * Rendering consists of three passes:
             * 
             * 1) Calculate Z-Values for all objects and sort them ascendingly for the next passes (IsSelectionPass == false, IsRenderPass == false)
             * 2) Rendering with selection shader to translate mouse into object selection (IsSelectionPass == true, IsRenderPass == true)
             * 3) Rendering with scene shader producing the pixels shown on screen (IsSelectionPass == false, IsRenderPass == true)
             */
            if (Parent.IsRenderPass)
            {
                var tex = Texture;

                if (tex != null)
                    tex.Bind();
                else
                    GL.BindTexture(TextureTarget.Texture2D, 0);

                Parent.DrawSprite(this, x, y, inWidth * Scale, inHeight * Scale);
            }
            else
            {
                ZValue = (y + inHeight + m_RenderShift) / 1000;
            }
        }
    }
}
